package zdream.control.world;

/**
 * <p>用于时间管理.
 * <p>一般而言, 每秒走 60 帧.
 * <p>这里需要区分两个概念, 一个是游戏画面的帧时长, 一个是游戏世界 {@link LevelWorld} 的帧时长.
 * 一般游戏画面走一帧, 游戏世界会走一帧, 在画面卡顿的时候有可能走好几帧.
 * 为了区分画面帧 (real time frame) 和游戏世界帧 (world time frame), 我们确定了这么几个规则:
 *
 * <li>游戏世界时间使用 activeTime 和 worldTime 两个时间, 前者不含房间切换的时间
 * <li>画面帧使用 frame 这个单词. 游戏世界的时间都不允许使用 frame 这个单词.
 * <li>游戏世界使用 tick 这个单词. 画面帧不允许使用 tick 这个单词.
 * </li>
 * </p>
 *
 * @author Zdream
 * @date 2020-06-08
 */
public class Ticker {
	/* ***********
	 * Real Time *
	 *********** */

	public long worldTimeWhenFrameStart, activeTimeWhenFrameStart;
	public long worldTimeWhenFrameFinished, activeTimeWhenFrameFinished;
	public long durationInFrame;
	public float delta;
	public boolean paused;
	public Runnable everyTick = () -> {}, afterFrame = () -> {};

	public long maxTickSecondsPerFrame = 16, minTickSecondsPerFrame = 4;
	public float lastFrameRemain;

	public void frameStart() {
		worldTimeWhenFrameStart = worldTime;
		activeTimeWhenFrameStart = activeTime;
		durationInFrame = 0;
	}

	public void frameFinished() {
		worldTimeWhenFrameFinished = worldTime;
		activeTimeWhenFrameFinished = activeTime;
		durationInFrame = worldTimeWhenFrameFinished - worldTimeWhenFrameStart;
	}

	public void runFrame(float delta) {
		this.delta = delta;
		frameStart();

		float max = maxTickSecondsPerFrame / 1000f;
		float min = minTickSecondsPerFrame / 1000f;
		if (!paused) {
			float remain = delta + lastFrameRemain;
			while (remain > max) {
				remain -= max;
				tick(maxTickSecondsPerFrame);
			}

			if (remain >= min) {
				long millis = (long) (remain * 1000);
				lastFrameRemain = remain - millis / 1000f;
				tick(millis);
			} else {
				lastFrameRemain = remain;
			}
		}
		frameFinished();
		afterFrame.run();
	}

	/* ************
	 * World Time *
	 ************ */

	/**
	 * 是否暂停. 这个参数指的是 foe 是否 active.
	 * 当暂停时 (foePaused = true), 再次调用 tick 方法, worldTime 会加, 但是 activeTime 不会加.
	 *
	 * foePaused 会在房间切换时会为 true
	 */
	public boolean foePaused;
	/**
	 * <p>游戏世界的时间（上一帧和当前帧）, 单位 : 毫秒
	 * 当游戏暂停时, 该时间暂停.
	 * 其它情况时 (包括切换房间) 该时间往上加
	 * </p>
	 */
	public long lastWorldTime = 0L, worldTime = 0L;
	/**
	 * <p>游戏世界 Foe 的活动时间（上一帧和当前帧）, 单位 : 毫秒
	 * 仅当游戏不暂停, 且 foeActive=true 时, 时间计算进去
	 * </p>
	 */
	public long lastActiveTime = 0L, activeTime = 0L;
	/**
	 * 上一帧和本帧的时间跨度, 用于计算. 超过 0.05 秒的计 0.05 秒. 单位秒
	 */
	public float duration = 0f;
	/**
	 * 上一帧和本帧的时间跨度, 用于计算. 超过 0.05 秒的计 0.05 秒. 单位毫秒
	 */
	public long lDuration = 0L;

	private void tick(long millis) {
		lDuration = millis;
		duration = millis / 1000f;
		lastWorldTime = worldTime;
		worldTime += lDuration;
		if (!foePaused) {
			lastActiveTime = activeTime;
			activeTime += lDuration;
		}
		everyTick.run();
	}
}
